// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <pollux/functions/prestosql/types/bing_tile_type.h>
#include <melon/expected.h>
#include <optional>
#include <string>

namespace kumo::pollux {
    std::optional<std::string> BingTileType::bingTileInvalidReason(uint64_t tile) {
        // TODO?: We are duplicating some logic in isBingTileIntValid; maybe we
        // should extract?

        uint8_t version = BingTileType::bingTileVersion(tile);
        if (version != BingTileType::kBingTileVersion) {
            return fmt::format("Version {} not supported", version);
        }

        uint8_t zoom = BingTileType::bingTileZoom(tile);
        if (zoom > BingTileType::kBingTileMaxZoomLevel) {
            return fmt::format(
                "Bing tile zoom {} is greater than max zoom {}",
                zoom,
                BingTileType::kBingTileMaxZoomLevel);
        }

        uint64_t coordinateBound = 1 << zoom;

        if (BingTileType::bingTileX(tile) >= coordinateBound) {
            return fmt::format(
                "Bing tile X coordinate {} is greater than max coordinate {} at zoom {}",
                BingTileType::bingTileX(tile),
                coordinateBound - 1,
                zoom);
        }
        if (BingTileType::bingTileY(tile) >= coordinateBound) {
            return fmt::format(
                "Bing tile Y coordinate {} is greater than max coordinate {} at zoom {}",
                BingTileType::bingTileY(tile),
                coordinateBound - 1,
                zoom);
        }

        return std::nullopt;
    }

    melon::Expected<uint64_t, std::string> BingTileType::bingTileParent(
        uint64_t tile,
        uint8_t parentZoom) {
        uint8_t tileZoom = bingTileZoom(tile);
        if (MELON_UNLIKELY(tileZoom == parentZoom)) {
            return tile;
        }
        uint32_t x = bingTileX(tile);
        uint32_t y = bingTileY(tile);

        if (MELON_UNLIKELY(tileZoom < parentZoom)) {
            return melon::makeUnexpected(fmt::format(
                "Parent zoom {} must be <= tile zoom {}", parentZoom, tileZoom));
        }
        uint8_t shift = tileZoom - parentZoom;
        return bingTileCoordsToInt((x >> shift), (y >> shift), parentZoom);
    }

    melon::Expected<std::vector<uint64_t>, std::string>
    BingTileType::bingTileChildren(uint64_t tile, uint8_t childZoom) {
        uint8_t tileZoom = bingTileZoom(tile);
        if (MELON_UNLIKELY(tileZoom == childZoom)) {
            return std::vector<uint64_t>{tile};
        }
        uint32_t x = bingTileX(tile);
        uint32_t y = bingTileY(tile);

        if (MELON_UNLIKELY(childZoom < tileZoom)) {
            return melon::makeUnexpected(fmt::format(
                "Child zoom {} must be >= tile zoom {}", childZoom, tileZoom));
        }
        if (MELON_UNLIKELY(childZoom > kBingTileMaxZoomLevel)) {
            return melon::makeUnexpected(fmt::format(
                "Child zoom {} must be <= max zoom {}",
                childZoom,
                kBingTileMaxZoomLevel));
        }

        uint8_t shift = childZoom - tileZoom;
        uint32_t xBase = (x << shift);
        uint32_t yBase = (y << shift);
        uint32_t numChildrenPerOrdinate = 1 << shift;
        std::vector<uint64_t> children;
        children.reserve(numChildrenPerOrdinate * numChildrenPerOrdinate);
        for (uint32_t deltaX = 0; deltaX < numChildrenPerOrdinate; ++deltaX) {
            for (uint32_t deltaY = 0; deltaY < numChildrenPerOrdinate; ++deltaY) {
                children.push_back(
                    bingTileCoordsToInt(xBase + deltaX, yBase + deltaY, childZoom));
            }
        }
        return children;
    }

    melon::Expected<uint64_t, std::string> BingTileType::bingTileFromQuadKey(
        const std::string_view &quadKey) {
        size_t zoomLevelInt32 = quadKey.size();
        if (MELON_UNLIKELY(zoomLevelInt32 > kBingTileMaxZoomLevel)) {
            return melon::makeUnexpected(fmt::format(
                "Zoom level {} is greater than max zoom {}",
                zoomLevelInt32,
                kBingTileMaxZoomLevel));
        }
        uint8_t zoomLevel = static_cast<uint8_t>(zoomLevelInt32);
        uint32_t tileX = 0;
        uint32_t tileY = 0;
        for (uint8_t i = zoomLevel; i > 0; i--) {
            int mask = 1 << (i - 1);
            switch (quadKey.at(zoomLevel - i)) {
                case '0':
                    break;
                case '1':
                    tileX |= mask;
                    break;
                case '2':
                    tileY |= mask;
                    break;
                case '3':
                    tileX |= mask;
                    tileY |= mask;
                    break;
                default:
                    return melon::makeUnexpected(
                        fmt::format("Invalid QuadKey digit sequence: {}", quadKey));
            }
        }
        return BingTileType::bingTileCoordsToInt(tileX, tileY, zoomLevel);
    }

    std::string BingTileType::bingTileToQuadKey(uint64_t tile) {
        uint8_t zoomLevel = bingTileZoom(tile);
        uint32_t tileX = bingTileX(tile);
        uint32_t tileY = bingTileY(tile);

        std::string quadKey;
        quadKey.resize(zoomLevel);

        for (uint8_t i = zoomLevel; i > 0; i--) {
            char digit = '0';
            int mask = 1 << (i - 1);
            if ((tileX & mask) != 0) {
                digit++;
            }
            if ((tileY & mask) != 0) {
                digit += 2;
            }
            quadKey[zoomLevel - i] = digit;
        }
        return quadKey;
    }
} // namespace kumo::pollux
